﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using MarkdownMonster.Annotations;

namespace MarkdownMonster
{

	/// <summary>
	/// Class wrapper around the WkPdfToHtml Engine
	/// https://wkhtmltopdf.org	
	/// </summary>
	public class HtmlToPdfGeneration : INotifyPropertyChanged
	{

		/// <summary>
		/// The document title. If null or empty the first 
		/// header is used which is the default..		
		/// </summary>
		public string Title { get; set; }

		/// <summary>
		///  The path to the wkpdftohtml executable (optional)
		/// </summary>
		public string ExecutionPath { get; set; }

		/// <summary>
		/// Documents paper size Letter, 
		/// </summary>
		public PdfPageSizes PageSize { get; set; }

		public PdfPageOrientation Orientation { get; set; }

		#region headers and footers
		
		public string FooterText { get; set; }

		public string FooterHtmlUrl { get; set; }

		
		public bool ShowFooterLine { get; set; }



		public int FooterFontSize { get; set; }

		public bool ShowFooterPageNumbers { get; set; }
		
		public string HeaderHtmlUrl { get; set; }
		
		public string HeaderLeft { get; set; }
		
		public string HeaderRight { get; set; }
		
		#endregion

		public int ImageDpi { get; set; } = 300;

        public bool GenerateTableOfContents { get; set; } = true;
        
        public bool DisplayPdfAfterGeneration { get; set; } = true;


	    

	    public string ExecutionOutputText
	    {
	        get { return _executionOutputText; }
	        set
	        {
	            if (value == _executionOutputText) return;
	            _executionOutputText = value;
	            OnPropertyChanged();
	        }
	    }
	    private string _executionOutputText;

        
	    public string FullExecutionCommand
	    {
	        get { return _fullExecutionCommand; }
	        set
	        {
	            if (value == _fullExecutionCommand) return;
	            _fullExecutionCommand = value;
	            OnPropertyChanged();
	            OnPropertyChanged(nameof(HasExecutionCommandLine));
            }
	    }
	    private string _fullExecutionCommand;

	    

	    public bool HasExecutionCommandLine
	    {
	        get { return !string.IsNullOrEmpty(FullExecutionCommand); }	    
	    }
	    public bool HasExecutionOutput
	    {
	        get { return !string.IsNullOrEmpty(FullExecutionCommand); }
	    }

        public PdfPageMargins Margins { get; set; } = new PdfPageMargins();



		public bool GeneratePdfFromHtml(string sourceHtmlFileOrUri, string outputPdfFile)
		{
			StringBuilder sb = new StringBuilder();

			// options
			sb.Append($"--image-dpi {ImageDpi} ");

			sb.Append($"--page-size {PageSize} ");
			sb.Append($"--orientation {Orientation} ");
		    sb.Append("--enable-internal-links ");
		    sb.Append("--keep-relative-links ");
		    sb.Append("--print-media-type ");

            sb.Append($"--footer-font-size {FooterFontSize} ");
			if (ShowFooterLine)
				sb.Append("--footer-line");

			// precedence: Html, text, page numbers
			if (!string.IsNullOrEmpty(FooterHtmlUrl))
			{
				sb.Append($"--footer-html \"{FooterHtmlUrl}\" ");
			}
			else if (!string.IsNullOrEmpty(FooterText))
			{
				sb.Append($"--footer-right \"{FooterText}\" ");
			}
			else if (ShowFooterPageNumbers)
			{
				sb.Append("--footer-right \"[page] of [topage]\" ");
			}

			if (!string.IsNullOrEmpty(Title))
			{				
				sb.Append($"--header-left \"{Title}\" ");
				//sb.Append($"--header-right \"[subsection]\" ");
				sb.Append("--header-spacing 3 ");
			}

			if (Margins.MarginLeft > 0)
				sb.Append($"--margin-left {Margins.MarginLeft} ");
			if (Margins.MarginRight > 0)
				sb.Append($"--margin-right {Margins.MarginRight} ");
			if (Margins.MarginTop > 0)
				sb.Append($"--margin-top {Margins.MarginTop} ");
			if (Margins.MarginBottom > 0)
				sb.Append($"--margin-bottom {Margins.MarginBottom} ");

		    if (!GenerateTableOfContents)
		    {
		        sb.Append($"--no-outline ");
                sb.Append($"--outline-depth 3 ");
		    }

		    // in and out files
			sb.Append($"\"{sourceHtmlFileOrUri}\" \"{outputPdfFile}\" ");

			string exe = "wkhtmltopdf.exe";
			if (!string.IsNullOrEmpty(ExecutionPath))
				exe = Path.Combine(ExecutionPath, exe );

			FullExecutionCommand = exe + " " + sb;

			try
			{
				File.Delete(outputPdfFile);
			}
			catch
			{
				SetError("Please close the output file first:\r\n" + outputPdfFile);
				return false;
			}


			int exitCode = ExecuteWkProcess(sb.ToString());

			if (exitCode != 0)
			{
				SetError("Unable to create PDF document. Exit code: " + exitCode + "\r\n" + sb);
				return false;
			}

			if (exitCode == 0 && DisplayPdfAfterGeneration)
			{
				try
				{
					Process.Start(outputPdfFile);
				}catch { }
			}

			return true;
		}

		public int ExecuteWkProcess(string parms, bool copyCommandLineToClipboard = false)
		{
			string exe = "wkhtmltopdf.exe";
			if (!string.IsNullOrEmpty(ExecutionPath))
				exe = Path.Combine(ExecutionPath, exe);

			StringBuilder output = new StringBuilder();

			FullExecutionCommand = "\"" + exe + "\" " + parms;
		    if (copyCommandLineToClipboard)
		        ClipboardHelper.SetText(FullExecutionCommand);

			using (Process process = new Process())
			{
				process.StartInfo.FileName = exe;
				process.StartInfo.Arguments = parms;
				process.StartInfo.UseShellExecute = false;
				process.StartInfo.CreateNoWindow = true;
				process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
				process.StartInfo.RedirectStandardOutput = true;
				process.StartInfo.RedirectStandardError = true;
				process.StartInfo.RedirectStandardInput = true;

				void OnProcessOnOutputDataReceived(object o, DataReceivedEventArgs e) => output.AppendLine(e.Data);

				process.OutputDataReceived += OnProcessOnOutputDataReceived;				
				process.ErrorDataReceived += OnProcessOnOutputDataReceived;

				try
				{
					process.Start();

					process.BeginOutputReadLine();
					process.BeginErrorReadLine();

					bool result = process.WaitForExit(180000);

					if (!result)
						return 1473;
				}
				finally
				{
					process.OutputDataReceived -= OnProcessOnOutputDataReceived;
					process.ErrorDataReceived -= OnProcessOnOutputDataReceived;
				}

				ExecutionOutputText = output.ToString();

				return process.ExitCode;

			}	
		}
	

		
		#region Error Message
		public string ErrorMessage { get; set; }

		protected void SetError()
		{
			SetError("CLEAR");
		}

		protected void SetError(string message)
		{
			if (message == null || message == "CLEAR")
			{
				ErrorMessage = string.Empty;
				return;
			}
			ErrorMessage += message;
		}

		protected void SetError(Exception ex, bool checkInner = false)
		{
			if (ex == null)
				ErrorMessage = string.Empty;

			Exception e = ex;
			if (checkInner)
				e = e.GetBaseException();

			ErrorMessage = e.Message;
		}
		#endregion

		public event PropertyChangedEventHandler PropertyChanged;

		[NotifyPropertyChangedInvocator]
		protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	public class PdfPageMargins
	{
		public int MarginLeft { get; set; }
		public int MarginRight { get; set; }
		public int MarginTop { get; set; }
		public int MarginBottom { get; set; } 
	}


	public enum PdfPageSizes
	{
		Letter,
		A4,
	}

	public enum PdfPageOrientation
	{
		Portrait,
		Landscape
	}

}
